/* 

==========================================================

DX490a - Summer 2010

Instructor: Stelios Manousakis

==========================================================

Class 12.1:

Graphic User Interfaces (GUI) in SuperCollider

Contents:

• General

• GUI Kits

• Basics

- Window

- decorators

- Resizing

• Performing actions

- Controlling a synth

• Scheduling

- Controlled by a synth

• More references

• Useful Quarks for GUI

• Architecture: Model-View-Controller

==========================================================

*/





// ================= GRAPHIC USER INTERFACES IN SUPERCOLLIDER =================



// ====== GENERAL ======


// SuperCollider is probably least famous for its GUI capabilities, but in all truth, it can be very powerful, allowing you to take advantage of its algorithmic functionality and language power to create some very good looking GUIs. Truth be told, it can take some lines of code to write GUIs... but the syntax is simple enough.


// In Mac OS X, you can press shift+cmd+N to see what the available GUI widgets are. Just drag anything in the window, toggle edit, and try it out.In this way, you can actually drag and drop and then position GUI widgets as you like in a window, and then get an automatically generated code!




// ====== GUI Kits ======


// You may remember from class 1 that there are different types of gui servers that one can use in SC: the standard CocoaGUI for Mac OS X, and SwingGUI (in Java; it is installed by default in Windows, but can also be used on Linux and OS X, although you will have to install it yourself); Swing OSC acts like a server, sending/receiving messages to/from sclang. There are specific subclasses for each GUI kit: the CocoaGUI kit uses the SC prefix (ex. SCWindow), and the Swing kit uses the JSC or J prefix (ex. JSCWindow, or JStethoscope). Luckily, you can write platform independent GUIs by using the classes without their prefix (ex. Window), which makes them work in any platform. For a table of all gui classes and their kit-dependent equivalents, see GUI-Classes.



// GUI is a class providing the means for writing platform-independent code, doing all the necessary translation from one scheme to the other. 

// You can ask the class what schemes are available in your machine:

GUI.schemes;


// Check which one you 're using at the moment:

GUI.current


// You can switch the GUI kit by calling the appropriate clas method:

GUI.cocoa; // use cocoa in subsequent GUI creation procedures

GUI.swing; // use swing in subsequent GUI creation procedures NOTE: If you do not have SwingOSC installed, you get a warning only, and do not switch; so you cannot accidentally disable your (mac) gui system.


// For more on gui kits, see the GUI helpfile




// ====== BASICS ======



// ------ Window --

(

//make a 300x100 window at 400x50 screen co-ordinates. 

/* Coordinates are and instance of Rect, calculated as:

origin from left of screen

origin from bottom of screen

size in x (width)

size in y (height)

*/

w = Window("Test window", Rect(400, 50, 300, 100)); // use Window for cross-platform compatibility

w.front; //this line is need to make the window actually appear

)


(

// you could also use screenbounds to position something according to the size of the screen

w = Window("Test window", Rect(Window.screenBounds.width-300, Window.screenBounds.height-500, 300, 100)); // use Window for cross-kit compatibility

w.front; //this line is need to make the window actually appear

)



// You can add any GUI element to the window:

(

w = Window("Test window", Rect(100, 500, 300, 300)); 

Slider(w, Rect(20, 60, 250, 20));

Button(w, Rect(20, 120, 150, 20))

.states_([["a button"], ["yes, still a button"], ["3rd state"]]);

w.front; 

)


// ------ decorators --

// Instead of manually defining where UI elements should be positioned inside a window, you could also use FlowLayout to position things for you:


(

// Window returns the window class for the current kit

w = Window( "my name is...", Rect( 128, 64, 340, 360 ));


w.view.decorator = FlowLayout( w.view.bounds ); // comment this out for no decorator

// w.addFlowLayout // you can use this instead of the above line for brevity.

w.view.background = Color( 0.6, 0.8, 0.8 );


32.do({ arg i;

// Here Button returns the button class for the current kit

b = Button( w, Rect( rrand( 20, 300 ), rrand( 20, 300 ), 75, 24 ));

b.states = [[ "Start " ++ i, Color.black, Color.rand ],

[ "Stop " ++ i, Color.white, Color.red ]];

});


w.front;

)

// Try uncommenting the FlowLayout line - what a mess!


// Nevertheless, doing this algorithmically will always be more precise and versatile


(

var window, size, offset, btnAmt, mod;

size = 75;

offset = 5;

btnAmt = 32;

mod = 4;


window = Window( "Algorithmically created", Rect( 128, 64, (size * mod) + offset, (size * btnAmt) / mod + offset ));


window.view.background = Color( 0, 0.3, 0.2 );


// use an array to be able to access the elements afterwards

~btnArray = Array.fill(btnAmt, {arg i;

// Here Button returns the button class for the current kit

Button( window, Rect((i%mod) * size + offset, i.div(mod) * size + offset, size - 5, size - 5))

.states = [[ "Start " ++ i, Color.black, Color.rand ],

[ "Stop " ++ i, Color.white, Color.red ]];

});

window.front;

)


// Change font on 0-15 buttons (you will need to refocus on the Window to see the effect):

(

16.do({ |i|

~btnArray[i].font_(Font("Monaco", 12))

})

)




// ------ resizing --

// For resizing GUIs when resizing their window, look at resize





// ====== PERFORMING ACTIONS ======

// All GUIs have an action method that may contain a function to be evaluated when a user interacts with the GUI


(

w = Window ("A Slider");

a = Slider (w, Rect(40, 10, 300, 30));

a.action={ arg sl; sl.value.postln }; // set the action of the slider

w.front

);

// add and remove actions on the fly

f = ({ |sl| w.view.background = Color.green(sl.value) });

a.addAction(f);

a.removeAction(f);

// you can remove all actions simultaneously

a.action = nil;




// ------ Controlling a synth --


s.boot;

// The synth

(

g = CtkSynthDef(\gray, {arg outBus = 0, freq = 400, amp = 1, gate = 1;

var env, src, fdbin, fdbout;

env = EnvGen.kr(Env([0, 1, 0], [0.05, 0.95], \sin, 1), gate, levelScale: amp, doneAction: 2);

src = LPF.ar(GrayNoise.ar(amp), freq, env);

Out.ar(outBus, Pan2.ar(src, Rand.new(-0.7, 0.7)));

});

)

h = g.new().play

h.release



// The GUI

(

var spec;

w = Window ("GrayNoise", Rect( 128, 64, 310, 80 ));

w.view.background_(Color.red(0.4));

w.view.decorator = FlowLayout( w.view.bounds );

b = Button( w, Rect( 10, 10, 125, 24 ))

.states_([[ "Start ", Color.red, Color.black ],

[ "Stop ", Color.white, Color.red ]])

.mouseDownAction_({ |val|

val.value.postln;

c = case

{val.value == 0} {h = g.new.play}

{val.value == 1} {h.release}

});

c = Slider (w, Rect(40, 10, 300, 30));

c.background_(Gradient(Color.red(0.1), Color.red(0.8)));

spec = ControlSpec(20, 20000, 'exp', 10);  // args: min, max, curve, stepsize

c.action_({|sl| 

h.freq_(spec.map(sl.value));

}); // set the action of the slider

w.front

);





// ====== SCHEDULING ======

// You may remember from the class on timing (3.1) that GUI runs on a lower priority thread in SC to save up resources for more important processes. Therefore, if you want to control a GUI with a timed process you need to use the AppClock or .defer the function.


// don't try this if you 're epileptic!

(

var w, r;

w = Window.new("trem", Rect(0, 0, Window.screenBounds.width, Window.screenBounds.height));

w.front;

r = Routine({ arg time;

200.do({ arg i;

0.01.yield;

{// must enclose this in a defered loop!

w.view.background_(Color.rand);

}.defer; // Notice the .defer here - otherwise it won't work!

});

1.yield;

w.close;

});

SystemClock.play(r);

)




// ------ Controlled by a synth --


// You can use a function to poll values, like a Routine, or an OSCResponder to get data back from the server. In either case, you need to defer the routine or responder, as they will be driving a GUI


(

w = Window("Frequency Monitor", Rect(200, Window.screenBounds.height-200,300,150)).front;

w.view.background_(Color.grey(0.9));

a = StaticText(w, Rect(45, 10, 200, 20)).background_(Color.rand);

a.string = "Current Frequency";

Button.new(w, Rect(45, 70, 200, 20)).states_([["close",Color.black,Color.rand]]).action_({w.close});

s.waitForBoot({

b = Bus.new(\control,0,1); // a bus, used to write values in it from scsynth and then get them back to sclang

q = SynthDef(\Docs_FreqMonitor, {var freq,snd;

freq=LFNoise0.ar(2, 400, 650);

snd=SinOsc.ar(freq,0,0.2);

Out.ar(0,snd);

Out.kr(b.index,freq); // output the frequency to a control bus

}).play;

r= Routine{

{ // Set the value of the StaticText to the value in the control bus.

// Setting GUI values is asynchronous, so you must use .defer in the system clock.

// Also you must check if the window is still open, since Routine will continue for at least // one step after you close the window.

b.get( {arg v; {w.isClosed.not.if{ a.string= " Current Frequency: "++v.round(0.01)}; }.defer} );

// b.get sends query to the server, and waits for a response before it sets the StaticText.

0.01.wait;

}.loop

}.play

});

CmdPeriod.doOnce({w.close});

w.onClose={r.stop; q.free; b.free }; //clean up if the window closes

)




// ====== MORE REFERENCES ======


// For more, have a look at the following files:

GUI-Overview

GUI-Classes

Document.open("examples/GUI examples/GUI_examples1.scd")

Document.open("examples/GUI examples/GUI_examples2.scd")

// Also, if you want to write your own GUI widget classes, look here:

SCUserView-Subclassing





// ====== USEFUL QUARKS FOR GUI ======

// Here is a list with some useful GUI quarks that you may want to get:

Automation

ClockFace

FileListView

ixiViews

PopUpTreeMenu

TabbedView

VuView

wslib // a quark library with many - and the best looking - GUIs





// ====== ARCHITECTURE: MODEL-VIEW-CONTROLLER ======


// Similarily to the architectural ideas presented in Class11.1 for dealing with large projects, it is much wiser to aim for a modular structure with your programs when dealing with GUIs, so that you can change elements of the GUI, the actual control and mapping, or the synthesis engine you're using, without breaking things. Also, the GUI should only be there to help you control things, and not trusted with delicate timing, as it runs on a lower priority thread.


// Having said that, a good design software architecture paradigm is the Model-View-Controller (MVC). For example, in a musical instrument/system, the Model is the sound synthesis part, the Controller is the functions, procmods, mapping, etc - anything that can receive input, interpret it and send the appropriate commands to the Model, and the View is your GUI, sending user commands to the Controller, but also potentially receiving commands from it for state updates. If you  use both a hardware interface and a GUI, then the hardware should be directly linked to your Controller, not the GUI.


// You can find more info on MVC here:

"open http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller".unixCmd